package com.torbinsky.esw.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.vfs2.FileChangeEvent;
import org.apache.commons.vfs2.FileContent;
import org.apache.commons.vfs2.FileListener;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileType;

/**
 * Listens to {@link FileChangeEvent}'s and responds to new files being created
 * by parsing them to check for hostname and IP address received-by lines.
 * 
 * @author Torben Werner
 *
 */
public class WatchedDirectoryListener implements FileListener {
	private static final Logger log = Logger.getLogger(WatchedDirectoryListener.class.getName());
	
	private final FileObject hostnameOutFile;
	private final FileObject ipOutFile;
	private final Pattern receivedByPattern;

	private final String receivedByAddress;

	/**
	 * @param hostnameOutFile the file which we will output matched hostname entries
	 * @param ipOutFile the file which we will output matched IP address entries
	 * @param receivedByAddress the address of the mail server which we are interested in the received-by line
	 */
	public WatchedDirectoryListener(FileObject hostnameOutFile, FileObject ipOutFile, String receivedByAddress) {
		this.hostnameOutFile = hostnameOutFile;
		this.ipOutFile = ipOutFile;
		this.receivedByAddress = receivedByAddress;
		receivedByPattern = Pattern.compile(RECEIVED_FROM_PATTERN);
		log.info("Matching for receive by server [" + receivedByAddress + "]");
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.vfs2.FileListener#fileCreated(org.apache.commons.vfs2.FileChangeEvent)
	 */
	public void fileCreated(FileChangeEvent event) throws Exception {
		log.info("file created - " + event.getFile().getName());
		if(isParseableFile(event.getFile())){
			parseAndOutputMatches(event.getFile());
		}
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.vfs2.FileListener#fileDeleted(org.apache.commons.vfs2.FileChangeEvent)
	 */
	public void fileDeleted(FileChangeEvent event) throws Exception {
		log.info("file deleted - " + event.getFile().getName());
		// no-op (don't care when files are deleted)
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.vfs2.FileListener#fileChanged(org.apache.commons.vfs2.FileChangeEvent)
	 */
	public void fileChanged(FileChangeEvent event) throws Exception {
		log.info("file changed - " + event.getFile().getName());
		// no-op (we aren't handling modified files)
	}
	
	/**
	 * @param fileInQuestion the file which we want to ensure is not one of our own output files
	 * @return true if the file is not one of the program's own output files, false if the file is
	 * @throws FileSystemException 
	 */
	private boolean isParseableFile(FileObject fileInQuestion) throws FileSystemException{
		// Don't parse our own output file if it happens to be in the watch directory
		if(fileInQuestion.equals(hostnameOutFile) || fileInQuestion.equals(ipOutFile)){
			log.fine("file detected as output file");
			return false;
		}
		
		// Only want to parse files.
		if(fileInQuestion.getType() != FileType.FILE){
			log.fine("file object is not a file (i.e. is a directory or something).");
			return false;
		}
		
		// File is not the program's own output file
		return true;
	}
	
	private void parseAndOutputMatches(FileObject file) throws IOException {
		log.fine("Checking for match...");
		// Open our file's contents so we can read it two lines at a time
		FileContent fileContent = file.getContent();
		InputStream in = null;
		try{
			in = fileContent.getInputStream();
			InputStreamReader is = new InputStreamReader(in);
			BufferedReader br = new BufferedReader(is);		
	
			// Scan the file 2 lines at a time until we match the pattern or run out of lines...
			String prevLine = br.readLine();
			String line = br.readLine();
			if(line == null || prevLine == null){
				log.info("There aren't 2 lines to scan for [" + file.getName() + "] - no matches will be made for this file.");
				return;
			}
			
			String currentAndPreviousLine = line.concat(prevLine);
			
			Matcher m = receivedByPattern.matcher(currentAndPreviousLine);
			while(line != null && prevLine != null) {
				// We only want the lines specific to the receive by server of interest
				if(line.contains("by " + receivedByAddress)){
					log.finest("Checking for match against lines " + currentAndPreviousLine);
					// Check for a match and append if found
					if(m.find()){					
						String hostMatch = m.group(1);
						String ipMatch = m.group(3);
						log.info("Match found for host [" + hostMatch + "] and ip [" + ipMatch + "]");
						appendToOutputFiles(hostMatch, ipMatch);
					}
				}
				
				// Roll over to the next line
				prevLine = line;
			    line = br.readLine();			    
			    
			    // If we have another line reset the matcher for the next go-around
			    if(line != null){
			    	currentAndPreviousLine = line.concat(prevLine);
			    	m.reset(currentAndPreviousLine);
			    }
			}
		} catch(FileSystemException e){
			log.severe("Encountered exception while parsing for pattern match.");
			e.printStackTrace();
		} finally {
			log.fine("Finished checking for match...");
			// Close our input stream if we had a problem
			if(in != null){
				in.close();
			}
		}
	}
	
	private void appendToOutputFiles(String hostMatch, String ipMatch) throws FileSystemException {
		appendLineToFileObject(hostnameOutFile, hostMatch + " REJECT\n");
		appendLineToFileObject(ipOutFile, ipMatch + " REJECT\n");
	}
	
	private void appendLineToFileObject(FileObject file, String line) throws FileSystemException {
		PrintStream printStream = null;
		try {
			// Append the line to the file's output stream and then close it
			printStream = new PrintStream(file.getContent().getOutputStream(true));
			printStream.append(line);
			printStream.flush();
		} finally {
			// Clean up our file's output stream no matter what
			if(printStream != null){
				printStream.close();
			}
		}
		
	}
	
	/*
	 * REGULAR EXPRESSION PATTERN MATCHING
	 */
	private static final String RECEIVED_FROM_PATTERN = ".*?Received:\\sfrom\\s+((\\w|\\.)+).*?(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})";
}
